﻿using Nop.Core;
using Nop.Core.Domain.Logistics;
using Nop.Services.Common;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Nop.Services.Logistics
{
    public enum ChargeRule
    {
        Membership,
        Volume,
        PayPerUse,
        StoreManager
    }

    public static class PackageChargeHelper
    {
        public static void GetCharge(Charge charge, ChargeRule rule, List<PackageInformation> packages)
        {
            if (charge == null)
            {
                throw new ArgumentNullException("charge");
            }

            if (packages == null)
            {
                throw new ArgumentNullException("packages");
            }

            bool isInterDistrict = packages.FirstOrDefault(x =>
                (x.AddtionalAttributes & AddtionalAttributes.InterDistrict) == AddtionalAttributes.InterDistrict) != null;
            if (isInterDistrict)
            {
                if (!packages.All(x =>
                    (x.AddtionalAttributes & AddtionalAttributes.InterDistrict) == AddtionalAttributes.InterDistrict))
                {
                    throw new InvalidOperationException("All packages must be inter-district.");
                }
            }

            IPackageChargeHandler handler = null;

            switch (rule)
            {
                case ChargeRule.Membership:
                    handler = new MembershipPackageChargeHandler();
                    break;
                case ChargeRule.Volume:
                    handler = isInterDistrict ?
                        new MembershipPackageChargeHandler() as IPackageChargeHandler
                        : new VolumePackageChargeHandler() as IPackageChargeHandler;
                    break;
                case ChargeRule.PayPerUse:
                    handler = new PayAsYouGoPackageChargeHandler();
                    break;
                case ChargeRule.StoreManager:
                    handler = new StoreManagerPackageChargeHandler();
                    break;
                default:
                    throw new NopException("Package charge handler cannot be found. {0}", rule.ToString());
            }

            packages.ForEach(p => charge.AddtionalCharges.Add(AddtionalChargeHelper.GetAddtionalCharge(p)));
            charge.PackageCharges.AddRange(handler.GetCharge(packages.ToArray(), charge.Warnings));
        }
    }

    public interface IPackageChargeHandler
    {
        decimal[] GetCharge(PackageInformation[] packageInfo, IList<string> warnings);
    }

    public abstract class DimensionChargeHandler : IPackageChargeHandler
    {
        public virtual decimal[] GetCharge(PackageInformation[] packageInfo, IList<string> warnings)
        {
            var pc = new decimal[packageInfo.Length];
            for (int i = 0; i < packageInfo.Length; i++)
            {
                pc[i] = GetPackageCharge(packageInfo[i], warnings);
            }
            return pc;
        }

        protected virtual decimal GetPackageCharge(PackageInformation packageInfo, IList<string> warnings)
        {
            if (packageInfo.PackagePreset.HasValue)
            {
                decimal presetRate = Decimal.Zero;
                switch (packageInfo.PackagePreset)
                {
                    case (int)PackagePreset.Letter:
                        presetRate = 3.99M;
                        break;
                    case (int)PackagePreset.Small:
                        presetRate = 4.99M;
                        break;
                    case (int)PackagePreset.Medium:
                        presetRate = 6.99M;
                        break;
                    case (int)PackagePreset.Large:
                        presetRate = 8.99M;
                        break;
                    case (int)PackagePreset.ExtraLarge:
                        presetRate = 12.99M;
                        break;
                }
                return presetRate;
            }
            else
            {
                decimal[,] rate = new decimal[,] { 
                    { 3.99M,  4.99M,  6.99M,  8.99M, 10.99M, 16.99M, 39.99M, 59.99M }, 
                    {     0,  4.99M,  6.99M,  8.99M, 10.99M, 17.99M, 39.99M, 59.99M }, 
                    {     0,  5.99M,  7.99M,  9.99M, 11.99M, 18.99M, 49.99M, 59.99M }, 
                    {     0,  6.99M,  8.99M,  9.99M, 12.99M, 19.99M, 49.99M, 59.99M }, 
                    {     0,  7.99M, 10.99M, 10.99M, 13.99M, 20.99M, 49.99M, 59.99M },
                    {     0,  8.99M, 11.99M, 11.99M, 14.99M, 21.99M, 49.99M, 59.99M },
                    {     0,  9.99M, 13.99M, 13.99M, 15.99M, 22.99M, 49.99M, 59.99M }
                };

                decimal totalLength = 0;
                bool isLetterSizeDimension = packageInfo.IsLettersize;
                bool isCompactSizeDimension = packageInfo.IsCompactSize;
                bool isFullSizeDimension = packageInfo.IsFullSize;
                bool isMinimumSizeDimension = false;
                if (packageInfo.Height.HasValue && packageInfo.Width.HasValue && packageInfo.Length.HasValue)
                {
                    totalLength = MathEx.Round(packageInfo.Height.Value + packageInfo.Width.Value + packageInfo.Length.Value, 0);
                    // Determine if individual dimensions satisfy letter size rules
                    decimal[] dimensions = { packageInfo.Height.Value, packageInfo.Width.Value, packageInfo.Length.Value };
                    if (!dimensions.Any(d => d > 12M) && dimensions.Count(d => d > 9M) <= 1 && dimensions.Count(d => d > 1M) <= 2)
                    {
                        // Condition: none should be larger than 12, at most 1 larger than 9 and at most another one larger than 1.
                        isMinimumSizeDimension = true;
                    }
                }

                int lenRate = 0;

                if (totalLength <= 24M)
                {
                    lenRate = 1;
                }
                else if (totalLength >= 25M && totalLength <= 48M)
                {
                    lenRate = 2;
                }
                else if (totalLength >= 49M && totalLength <= 55M)
                {
                    lenRate = 3;
                }
                else if (totalLength >= 56M && totalLength <= 63M)
                {
                    lenRate = 4;
                }
                else if (totalLength >= 64M && totalLength <= 71M)
                {
                    lenRate = 5;
                }
                else if (totalLength >= 72M && totalLength <= 79M)
                {
                    lenRate = 6;
                }
                else if (totalLength >= 80M && totalLength <= 118M)
                {
                    lenRate = 7;
                }
                else
                {
                    warnings.Add(string.Format("The total length is invalid: {0} in.", totalLength));
                    return 0;
                }

                int weightRate = 0;

                if (packageInfo.Weight.HasValue)
                {
                    packageInfo.Weight = MathEx.Round(packageInfo.Weight.Value, 0);
                }

                if (!packageInfo.Weight.HasValue || packageInfo.Weight.Value <= 5M)
                {
                    // Standard
                    weightRate = 0;
                }
                else if (packageInfo.Weight.Value >= 6M && packageInfo.Weight.Value <= 11M)
                {
                    weightRate = 1;
                }
                else if (packageInfo.Weight.Value >= 12M && packageInfo.Weight.Value <= 22M)
                {
                    weightRate = 2;
                }
                else if (packageInfo.Weight.Value >= 23M && packageInfo.Weight.Value <= 33M)
                {
                    weightRate = 3;
                }
                else if (packageInfo.Weight.Value >= 34M && packageInfo.Weight.Value <= 44M)
                {
                    weightRate = 4;
                }
                else if (packageInfo.Weight.Value >= 45M && packageInfo.Weight.Value <= 55M)
                {
                    weightRate = 5;
                }
                else if (packageInfo.Weight.Value >= 56M && packageInfo.Weight.Value <= 66M)
                {
                    weightRate = 6;
                }
                else
                {
                    warnings.Add(string.Format("The total weight is invalid: {0} lb.", packageInfo.Weight.Value));
                    return 0;
                }

                if (isLetterSizeDimension || (totalLength <= 22M && isMinimumSizeDimension && packageInfo.Weight.HasValue && packageInfo.Weight.Value <= 2M))//packageInfo.IsLettersize
                {
                    // Letter
                    lenRate = 0;
                    weightRate = 0;
                }
                if (isCompactSizeDimension)
                {
                    //Economy size:  (L + W + H) 48 in. weight 11 lb
                    lenRate = 2;
                    weightRate = 1;
                }
                if (isFullSizeDimension)
                {
                    //Economy size:  (L + W + H) 71 in. weight 33 lb
                    lenRate = 5;
                    weightRate = 3;
                }
                return rate[weightRate, lenRate] <= 0M ? rate[0, 0] : rate[weightRate, lenRate];
            }
        }
    }


    public class MembershipPackageChargeHandler : DimensionChargeHandler
    {
    }

    public class VolumePackageChargeHandler : IPackageChargeHandler
    {
        public decimal[] GetCharge(PackageInformation[] packageInfo, IList<string> warnings)
        {
            var pc = new decimal[packageInfo.Length];

            if (packageInfo.Length <= 0 || packageInfo.Length > 6)
            {
                warnings.Add(String.Format("The package number ({0}) is invalid for the volume shipper.", packageInfo.Length));
                return pc;
            }

            decimal totalWeight = 0M;
            bool hasNoneOversize = false;

            for (int i = 0; i < packageInfo.Length; i++)
            {
                bool isStandard = packageInfo[i].IsLettersize || packageInfo[i].IsCompactSize || packageInfo[i].IsFullSize;

                pc[i] = 0M;
                decimal totalLen = 0;
                if (packageInfo[i].Height.HasValue && packageInfo[i].Width.HasValue && packageInfo[i].Length.HasValue)
                {
                    totalLen = MathEx.Round(packageInfo[i].Height.Value + packageInfo[i].Width.Value + packageInfo[i].Length.Value, 0);
                    if (totalLen > 118)
                    {
                        warnings.Add(String.Format("The package #{0} has invalid dimension: {1} in.", i, totalLen));
                    }

                    if (packageInfo[i].Length.Value > 63 || packageInfo[i].Width.Value > 63 || packageInfo[i].Height.Value > 63)
                    {
                        warnings.Add(String.Format("The package #{0} has invalid dimonsion: {1}x{2}x{3} in.", i, packageInfo[i].Length, packageInfo[i].Width, packageInfo[i].Height));
                    }
                }
                else if (!isStandard)
                {
                    warnings.Add(String.Format("The package #{0} has no dimension information.", i));
                }

                if (!packageInfo[i].Weight.HasValue && !isStandard)
                {
                    warnings.Add(String.Format("The package #{0} has no weight information.", i));
                }
                else if (packageInfo[i].Weight.HasValue && packageInfo[i].Weight.Value > 66)
                {
                    warnings.Add(String.Format("The package #{0} exceeds 66 lb.", i));
                }

                // Over weight
                if (packageInfo[i].Weight.HasValue)
                {
                    totalWeight += MathEx.Round(packageInfo[i].Weight.Value, 0);
                    if (totalWeight > 88)
                    {
                        totalWeight -= 88;
                        pc[i] += 6;
                    }
                }

                // Over size
                if (totalLen > 79)
                {
                    pc[i] += 20;
                }
                else
                {
                    hasNoneOversize = true;
                }
            }

            // Base charge
            if (hasNoneOversize)
            {
                pc[0] += 6;
            }
            return pc;
        }
    }

    public class PayAsYouGoPackageChargeHandler : DimensionChargeHandler
    {
    }

    public class StoreManagerPackageChargeHandler : IPackageChargeHandler
    {
        private decimal lowRate;
        private decimal regularRate;
        public StoreManagerPackageChargeHandler()
        {
            var settings = Nop.Core.Infrastructure.EngineContext.Current.Resolve<LogisticsInformationSettings>();
            lowRate = settings.StoreOrderLowRate;
            regularRate = settings.StoreOrderRate;
        }

        public virtual decimal[] GetCharge(PackageInformation[] packageInfo, IList<string> warnings)
        {
            return packageInfo
                .Select(pi => pi.AddtionalAttributes.HasFlag(AddtionalAttributes.StoreOrderLowCharge) ? lowRate : regularRate)
                .ToArray();
        }
    }
}
